'''
See readme.txt for module and ili9431  information
'''

import time
import struct
import socket 
import framebuf
from micropython import const


RDDSDR  = const(0x0f) 
SLPOUT = const(0x11)        # 关闭随眠模式,lcd开始工作
GAMSET = const(0x26)        # 选择伽马值
DISPOFF = const(0x28)       # 帧内存输出被禁止
DISPON = const(0x29)        # !DISPON
CASET = const(0x2a)         # 帧内存的区域
PASET = const(0x2b)         # 能够访问的区域
RAMWR = const(0x2c)         # 数据传送给帧内存(ili9431)
RAMRD = const(0x2e)         # 帧内存传送图形数据到lcd显示,,控制行列数据
MADCTL = const(0x36)        # 帧内存的读写描扫方向
#VSCRSADD = const(0x37)      # 滚动区域   
PIXSET = const(0x3a)        # 设置像素格式
FRMCTR1 = const(0xb1)
DISCTRL = const(0xb6)       # Display Function Control,图像显示错误可能有关
PWCTRL1 = const(0xc0) 
PWCTRL2 = const(0xc1)       # 电压控制
VMCTRL1 = const(0xc5)
VMCTRL2 = const(0xc7)
PWCTRLA = const(0xcb)
PWCRTLB = const(0xcf) 
PGAMCTRL  = const(0xe0) 
NGAMCTRL  = const(0xe1)
DTCTRLA = const(0xe8) 
DTCTRLB = const(0xea) 
PWRONCTRL = const(0xed)     
PRCTRL = const(0xf7) 
ENA3G = const(0xf2) 
CHUNK = const(1024)

# udp接受数据使用
h = 0

def color565(r, g, b):
    return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3


class ILI9341():
    
#--------------------------------初始化即底层代码-----------------------------------------------#
    def __init__(self, spi, cs, dc, bl, rst, w, h, r):
        self.spi = spi
        self.cs = cs
        self.dc = dc
        self.rst = rst
        self.bl = bl # 背光(LED)
        self.init_width = w
        self.init_height = h
        self.width = w
        self.height = h
        self.rotation = r
        self.cs.init(self.cs.OUT, value=1) # v = H, 串口初始化
        self.dc.init(self.dc.OUT, value=0) # v = L
        self.rst.init(self.rst.OUT, value=0)
        self.bl.init(self.bl.OUT, value=1)
        self.reset()
        self.init()

    def init(self):
        for command, data in (
            (RDDSDR, b"\x30\x80\x20"),
            (PWCRTLB, b"\x00\x81\x30"),
            (PWRONCTRL, b"\x64\x03\x12\x81"),
            (DTCTRLA, b"\x85\x00\x78"),
            (PWCTRLA, b"\x39\x2c\x00\x34\x02"),
            (PRCTRL, b"\x20"),
            (DTCTRLB, b"\x00\x00"),
            (PWCTRL1, b"\x23"), # 4.6v
            (PWCTRL2, b"\x10"),
            (VMCTRL1, b"\x3e\x28"),
            (VMCTRL2, b"\x86")):
            self.command(command, data)
    
        if self.rotation == 0:                  # 0 deg
            self.command(MADCTL, b"\x48")
            self.width = self.init_height
            self.height = self.init_width
        elif self.rotation == 1:                # 90 deg
            self.command(MADCTL, b"\x28")
            self.width = self.init_width
            self.height = self.init_height
        elif self.rotation == 2:                # 180 deg
            self.command(MADCTL, b"\x88")
            self.width = self.init_height
            self.height = self.init_width
        elif self.rotation == 3:                # 270 deg
            self.command(MADCTL, b"\xE8")
            self.width = self.init_width
            self.height = self.init_height
        elif self.rotation == 4:                # Mirrored + 0 deg
            self.command(MADCTL, b"\xC8")
            self.width = self.init_height
            self.height = self.init_width
        elif self.rotation == 5:                # Mirrored + 90 deg
            self.command(MADCTL, b"\x68")
            self.width = self.init_width
            self.height = self.init_height
        elif self.rotation == 6:                # Mirrored + 180 deg
            self.command(MADCTL, b"\x08")
            self.width = self.init_height
            self.height = self.init_width
        elif self.rotation == 7:                # Mirrored + 270 deg
            self.command(MADCTL, b"\xA8")
            self.width = self.init_width
            self.height = self.init_height
        else:
            self.command(MADCTL, b"\x48")
        for command, data in (
            (PIXSET, b"\x65"), #  RGB 5-6-5-bit
            (FRMCTR1, b"\x00\x18"),
            (DISCTRL, b"\x08\x82\x27"),
            (ENA3G, b"\x00"),
            (GAMSET, b"\x01"), #gamma curve 1(G2,2)
            (PGAMCTRL, b"\xd0\x04\x0d\x11\x13\x2b\x3f\x54\x4c\x18\x0d\x0b\x1f\x23"),
            (NGAMCTRL, b"\xd0\x04\x0c\x11\x13\x2c\x3f\x44\x51\x2f\x1f\x1f\x20\x23")):
            self.command(command, data)
        self.command(SLPOUT)
        time.sleep_ms(120)
        self.command(DISPON)
        
    def reset(self):
        self.bl.value(0)
        self.rst.value(0)
        time.sleep_ms(50)
        self.rst.value(1)
        time.sleep_ms(50)        
    #写数据
    def data(self, data):
        self.dc.value(1) # dc = H:Read data(esp32==>lcd)
        self.cs.value(0) # cs = L:Can write instructions and data
        self.spi.write(data)
        self.cs.value(1)
    #写命令
    def command(self, command, data = None):
        self.dc.value(0) # dc = L:Write com
        self.cs.value(0)
        self.spi.write(bytearray([command]))
        self.cs.value(1)
        if data  is not None:
            self.data(data)
    #写入块
    def writeblock(self, x0, y0, x1, y1):
        self.command(CASET)# 列地址设置
        self.data(struct.pack(">HH", x0, x1))
        self.command(PASET)# 页地址设置
        self.data(struct.pack(">HH", y0, y1))
        self.command(RAMWR)# 内存写入
    #读取块，返回
    def readblock(self, x0, y0, x1, y1):
        self.command(CASET)# 列地址设置
        self.data(struct.pack(">HH", x0, x1))
        self.command(PASET)# 页地址设置
        self.data(struct.pack(">HH", y0, y1))
        self.command(RAMRD)# 内存读出
        data = self.spi.read((x1 - x0 + 1) * (y1 - y0 + 1) * 3)
        return data
    
    #-----------------------------------实现FrameBuffer类方法----------------------------------------#
#这些方法之前必须要先获取buffer在他们是buffer上操作的，即把屏幕的一块单独拿出来操作#

    def pixel(self,x,y,c = 0xffff):
        self.getbuffer(x,y,1,1)
        self.fb.pixel(0,0,c)
        self.show()
    def fill(self,x,y,w,h,c = 0xffff):
        self.getbuffer(x,y,w,h)
        self.fb.fill_rect(0,0,w,h,c)
        self.show()
    def rect(self,x,y,w,h,c = 0xffff):
        self.getbuffer(x,y,w,h)
        self.fb.rect(0,0,w,h,c)
        self.show()
    def line(self,x1,y1,x2,y2,c = 0xffff):
        w = x2-x1
        h = y2-y1
        self.getbuffer(x1,y1,w,h)
        self.fb.line(0,0,w,h,c)
        self.show()
    def hline(self,x,y,w,c = 0xffff):
        self.getbuffer(x,y,w,1)
        self.fb.hline(0,0,w,c)
        self.show()
    def vline(self,x,y,h,c = 0xffff):
        self.getbuffer(x,y,1,h)
        self.fb.vline(0,0,h,c)
        self.show()
    def text(self,str,x,y,c = 0xffff):
        self.getbuffer(x,y,8*len(str),8)
        self.fb.text(str,0,0,c)
        self.show()
 #---------------------------------------------------------------------------------------------#
 
 
 
 #---------------------------------实现图片(数组)显示由SD卡或网络传输--------------------------------#
       #图像数列大于240*160或者200*200，图像会显示错误
       #esp32在读取数列数组时，cpu堆栈只有15360字节，超出会导致数据读取出错
    def arrmap(self, x, y, pic_w, pic_h, imag_arr):
        lineh = 1
        size = pic_w * pic_h *2
        block = pic_w * 2
#         lines = 60
        lines = size // block
        
        self.writeblock(x,y,pic_w,pic_h)
        for v in range(0, lines+1):
            self.getbuffer(x ,y+v, pic_w , lineh)
            
            if v == lines: 
                self.buf = bytearray(imag_arr[(v * block):])
            else:
                self.buf = bytearray(imag_arr[(v * block):(v + 1) * block])
            print(type(self.buf))
            self.show()
    
    # 字典输出图像数据,解决图像数据大于240*160后,图像显示错误
    def dicmap(self, x, y, pic_w, pic_h, imag_dic):
        lineh = 1
        size = pic_w * pic_h *2
        block = pic_w * 2
        lines = size // block

        self.writeblock(x,y,pic_w,pic_h)
        for v in range(0, lines):
            self.buf = bytearray(imag_dic[v])
            self.show()
     
    
    
    # x,y图片起始显示位置
    # pic_w,pic_h图片宽高
    # 图片文件的路径,该函数需要将图片准换成RGB5-6-5的bin文件
    # 直接读取png/jpg将会转换单通道像素，无法将其转换成三通道像素
    # 不满足上述条件将导致图片显示混乱，不建议使用该方法
    def bitmap(self,x,y,pic_w,pic_h,file_name):
        lines = 1
        block = pic_w * lines * 2
        nums = (pic_w * pic_h * 2) // block
        with open(file_name,'rb') as fio:
          for v in range(nums):
            self.writeblock(x,y,pic_w,pic_h)
            self.getbuffer(x ,y+v*lines, pic_w ,lines)
            self.buf = fio.read(block)
            self.show()
            
    # tcp有应答接受图片信息
    def bitmap_tcp(self, sock, pic_w):
        lines = pic_w
        x = int(sock.recv(10))
        sock.send(b's')
        y = int(sock.recv(10))
        sock.send(b's')
        pic_w = int(sock.recv(10))
        sock.send(b's')
        pic_h = int(sock.recv(10))
        sock.send(b's')
        block = pic_w * lines * 2
        nums = (pic_w * pic_h * 2) // block
        for v in range(nums):
          self.getbuffer(x ,y+v*lines, pic_w ,lines)
          dat, _ = sock.recv(lines)
          self.buf = bytearray(dat[0:])
          self.show()
          time.sleep_ms(100)#延时等待刷新
    
    # udp进行实时接收,需要传递行标志位v
    def bitmap_udp(self, x, y, pic_w, pic_h, sock):
        global h
        lineh = 3
        size = pic_w*pic_h*50
        dat, _ = sock.recvfrom(size)  # udp接受视频数据
        if h != (pic_h +1):
            h += lineh
        elif h >= pic_h :
            h = lineh
        print(h)   
        self.writeblock(x, y, pic_w, pic_h)
        self.getbuffer(x, (y+(h-lineh)), pic_w, lineh)
        self.buf = dat
#         print(self.buf)
#         self.buf = bytearray(dat)
        self.show()
        
        
        
        
#---------------------------------------------------------------------------------------------#



#--------------------------------字符输入自动换行支持所有字符的输入--------------------------------#        

    #功能：传入黑白字库文件，支持自定义字体颜色，速度较慢

    def print(self,str,xstart,ystart,fg = 0xffff,bg = 0x0000,font_path="/sd/utf16.zk",size=16):
        def hex2bin(g):#将传入的颜色转为字节串
          s = hex(g).replace("0x","")
          while len(s)<4:s = '0' + s#补全为四位
          return bytearray([int(s[0:2],16),int(s[2:4],16)])
        _fg = hex2bin(fg)    #前景色
        _bg = hex2bin(bg)    #背景色
        pos_y = ystart
        charbytes = int((size**2)/8)#一个字符的所占的字节数，是跳转字库指针的最小单位 
        n = -1#字符计数器，表示当前显示的字符

        max = int(240/size)-1
        with open(font_path,'rb') as f:#打开字库
          for c in str:#分割字符串为字符
            n = n + 1
            v = 0#写入buf的计数器，每显示一个字符清零
            f.seek(ord(c)*charbytes)#跳转到字符c的位置
            chr_num = f.read(charbytes)#读取字符c,返回此字符对应的点阵数据
            pos_x = xstart + size * n #y的位置为起始值加上n个字符size

            if pos_x >= 240 - 1.1 * size:#如果当前超出行
              pos_y = pos_y + size#y位置向下偏移一个size<换行>
              pos_x = xstart
              n = -1
            self.getbuffer(pos_x,pos_y,size,size)#每次显示一个字符
            for byte in chr_num:#分割一个字符点阵数据为字节，并将整个字符入self.buf等待显示
              x = bin(byte).replace('0b','')

              while len(x)<8:#补全为8位
                x = '0' + x
              for r in x:#将黑白字符转为彩色字符放在self.buf中
                if r == '0':
                  self.buf[0+2*v:2+2*v] = _bg
                else:
                  self.buf[0+2*v:2+2*v] = _fg

                v = v + 1

            self.show()#显示一个字符

    #功能：直接传入转化好的RGB565字库,不可改颜色，但速度更快    

    def printf(self,str,xstart,ystart,font_path="/sd/utf16.fzk",size=16):
        charbytes = int((size**2)*2)#一个字符的所占的字节数，是跳转字库指针的最小单位 
        n = -1
        pos_y = ystart
        max = int(240/size)-1
        with open(font_path,'rb') as f:#打开字库
          for c in str:#分割字符串为字符
            n = n + 1
            pos_x = xstart + size * n #y的位置为起始值加上n个字符size
            if pos_x >= 240 - 1.1 * size:#如果当前y位置超出屏幕
              pos_y = pos_y + size#x位置向下偏移一个size<换行>
              pos_x = xstart
              n = -1
            self.getbuffer(pos_x,pos_y,size,size)#每次显示一个字符
            f.seek(ord(c)*charbytes)#跳转到字符c的位置

            self.buf = f.read(charbytes)#读取字符c,返回此字符对应的点阵数据

            self.show()#显示一个字符
#---------------------------------------------------------------------------------------------#
 
 
 
#----------------------------------------其它方法-------------------------------------------#       
    def show(self):
        self.data(self.buf)
        
    def erase(self,c=0x0000): 
        for x in range(64):
          self.fill(0,5*x,240,5,c)
          
    def getbuffer(self,x,y,w,h):
        self.writeblock(x,y,x+w-1,y+h-1)
        self.buf = bytearray(w*h*2)
        self.fb = framebuf.FrameBuffer(self.buf,w,h,framebuf.RGB565)
#---------------------------------------------------------------------------------------------#






        
















